/*! @mainpage GraphPath
 *
 * @section Introduzione
 * Programma per la creazione visuale di grafi e per il calcolo dei percorsi minimi.
 * @section Utilizzo
 * La creazione del grafo avviene in maniera visuale con alcuni semplici comandi:
 *  - Click-sinistro: Seleziona un nodo.
      - Trascinamento: Se è selezionato un arco, ne modifica il peso, altrimenti aggiorna le coordinate del nodo selezionato.
 *  - Click-destro: Crea un nuovo nodo connesso all'ultimo nodo selezionato, oppure connette due nodi esistenti.
 *
 * Il grafo è implementato dalla classe ::Grafo la quale si avvale delle classi ::Nodo e ::Arco
 *
 * Il calcolo dei percorsi minimi avviene con gli algoritmi di Dijkstra e Bellman-Ford
 * Rispettivamente implementati nelle classi ::Dijkstra e ::BellmanFord
 * Entrambe le classi specializzano la classe ::Algoritmo 
 *
 * Nella funzione ::main viene inizializzata l'interfaccia grafica e associati gli eventi.
 *
 * @author Massimiliano Ferretti matricola 79769
*/

#ifndef DEBUG_MODE
#define NDEBUG
#endif

#include <stdlib.h>
#include <gtk/gtk.h>
#include <cassert>
#include "grafo.h"
#include "dijkstra.h"
#include "bellmanford.h"


using namespace std;
static GtkBuilder *builder;
Grafo *grafo_corrente = NULL; //!< Grafo su cui vengono effettuate tutte le operazioni

char* file_corrente = NULL; //!< Memorizza l'eventuale file da cui è stato caricato il grafo, oppure l'ultimo file sul quale è stato salvato.

/** Forza l'aggiornamento del grafo.
 * Invalida la drawing area.
 */
void aggiorna_grafo()
{
    GtkDrawingArea *widget = GTK_DRAWING_AREA(gtk_builder_get_object(builder, "drawingarea1"));
    gtk_widget_queue_draw((GtkWidget*)widget);
}

/** Aggiunge il testo specificato alla TextView.
 * @param[in] text Il testo da aggiungere
 */
void append_log(const char* text)
{
  GtkTextView *textview_log = GTK_TEXT_VIEW(gtk_builder_get_object(builder, "textviewLog")) ;
  GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview_log);
  GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
  GtkTextIter ei;
  
  gtk_text_buffer_get_end_iter(buffer, &ei);
  gtk_text_buffer_move_mark(buffer, mark, &ei);  
  gtk_text_buffer_insert_at_cursor(buffer, text, -1 );
  gtk_text_view_scroll_to_mark( textview_log, mark, 0.0, TRUE, 0.5, 1 );
}

/** Esegue un'istanza di ::Algoritmo.
 * @param[in] algoritmo Istanza della classe ::Algoritmo da invocare, può essere ::Dijkstra o ::BellmanFord
 */ 
void esegui_algoritmo(Algoritmo *algoritmo)
{
  assert(grafo_corrente != NULL);

  ostringstream oss;
  oss<<endl<<"Eseguo: "<<algoritmo->get_nome()<<endl;
  append_log(oss.str().c_str());
  algoritmo->esegui(append_log);  
  aggiorna_grafo();
  
  delete algoritmo;
}

/** Salva il grafo corrente su di un file selezionato.
 * La funzione mostra una finestra di selezione file standard.
 */
void salva_con_nome()
{
    GtkWindow *parent_window = NULL;
    GtkFileChooser *chooser;
    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
    gint res;

    GtkWidget* dialog = gtk_file_chooser_dialog_new ("Save File",
                                          parent_window,
                                          action,
                                          ("_Cancel"), GTK_RESPONSE_CANCEL,
                                          ("_Save"), GTK_RESPONSE_ACCEPT,
                                          NULL);
    chooser = GTK_FILE_CHOOSER (dialog);

    gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);

    if (file_corrente == NULL)
      gtk_file_chooser_set_current_name (chooser, ("grafo.txt"));
    else
      gtk_file_chooser_set_filename (chooser, file_corrente);

    res = gtk_dialog_run (GTK_DIALOG (dialog));
    if (res == GTK_RESPONSE_ACCEPT)
    {
        char *filename;
        filename = gtk_file_chooser_get_filename (chooser);
        
        grafo_corrente->salva(filename);
        
        if(file_corrente != NULL)
            g_free(file_corrente);
        file_corrente = filename;      
    }

    gtk_widget_destroy (dialog);
    
    append_log("File salvato\n");
}

/** Carica un grafo precedentemente salvato.
 * La funzione mostra una finestra di selezione file standard.
 */ 
void carica()
{
    assert(grafo_corrente != NULL);
    GtkWidget *dialog;
    GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
    gint res;

    dialog = gtk_file_chooser_dialog_new ("Carica un grafo",
                                          NULL,
                                          action,
                                          ("_Cancel"), GTK_RESPONSE_CANCEL,
                                          ("_Open"), GTK_RESPONSE_ACCEPT,
                                          NULL);

    res = gtk_dialog_run (GTK_DIALOG (dialog));
    if (res == GTK_RESPONSE_ACCEPT)
    {
        char *filename;
        GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
        filename = gtk_file_chooser_get_filename (chooser);

        grafo_corrente->carica(filename);

        if(file_corrente != NULL)
            g_free(file_corrente);
        file_corrente = filename;
    }

    gtk_widget_destroy (dialog);   
    aggiorna_grafo();    
    append_log("File caricato.\n");
}

extern "C" gboolean new_handler(GtkWidget * widget, gpointer data)
{
    grafo_corrente->inizializza();
    
    return FALSE;
}

extern "C" void help_handler(GtkWidget *widget, gpointer data)
{
    system("firefox ../doc/html/index.html &");
}

extern "C" gboolean quit_handler(GtkWidget *widget, gpointer data)
{
    gtk_main_quit();
    
    return TRUE;
}

extern "C" gboolean delete_event_handler(GtkWidget *widget, gpointer data)
{
    gtk_main_quit();
    
    return TRUE;
}

extern "C" void save_handler(GtkWidget *widget, gpointer data)
{
    assert(grafo_corrente != NULL);
    
    if(file_corrente != NULL)
        grafo_corrente->salva(file_corrente);
    else
        salva_con_nome();
}

extern "C" void saveas_handler(GtkWidget *widget, gpointer data)
{
    assert(grafo_corrente != NULL);
    salva_con_nome();
}

extern "C" gboolean open_handler(GtkWidget *widget, gpointer data)
{
	carica();
	
	return FALSE;
}

extern "C" gboolean esegui_bellmanford_handler(GtkWidget *widget, gpointer data)
{
    esegui_algoritmo(new BellmanFord(grafo_corrente));
    
    return FALSE;
}

extern "C" gboolean esegui_dijkstra_handler(GtkWidget *widget, gpointer data)
{
    esegui_algoritmo(new Dijkstra(grafo_corrente));
    
    return FALSE;
}

/** Comunica al ::Grafo di disegnare se stesso.
 * Il grafo si occuperà di comunicare a tutte le sue istanze di ::Nodo e ::Arco di disegnarsi
 */
extern "C" gboolean draw_handler(GtkWidget* widget, cairo_t *cr, gpointer data)
{
    gdouble width = gtk_widget_get_allocated_width(widget);
    gdouble height = gtk_widget_get_allocated_height(widget);

    // Disegna lo sfondo
    gdouble x = 0;

    while(x < width)
    {
        gdouble y = 0;
        while(y < height)
        {
            cairo_set_line_width (cr, 1.0);
            cairo_set_line_cap  (cr, CAIRO_LINE_CAP_ROUND); 
            cairo_move_to (cr, x, y); cairo_line_to (cr, x, y);
            cairo_stroke (cr);
        
            y += 25.0;
        }
        
        x += 25.0;
    }

    if(grafo_corrente != NULL)
        grafo_corrente->draw(cr);
    
    return FALSE;
}

/** Comunica agli oggetti che il mouse è stato spostato.
 * Se lo spostamento avviene durante la pressione del tasto sinistro, e l'oggetto selezionato è un ::Nodo, ne vengono aggiornate le coordinate.
 * Se l'oggetto selezionato è un ::Arco allora viene aggiornato il peso dell'arco in base allo scostamento verticale del mouse.
 * Se invece lo spostamento avviene senza la pressione del tasto sinistro, viene aggiornato il ::Grafo evidenziando solamente l'eventuale nodo presente sotto al mouse.
 */
extern "C" gboolean motion_notify_handler(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{
    assert(grafo_corrente != NULL);    
    grafo_corrente->update(event->x, event->y);

    aggiorna_grafo();

    return FALSE;
}

/** Comunica agli oggetti del ::Grafo che è stato premuto un pulsante.
 * Se si tratta del pulsante sinistro, gli oggetti si preparano per un eventuale operazione di trascinamento.
 * Se invece si tratta del pulsante destro del mouse, viene controllato se alle coordinate del click esiste già un nodo,
 * se esiste viene creato un arco tra il nodo precedentemente selezionato e quest'ultimo, altrimenti viene creato
 * un nuovo nodo connesso al precedente.
 * Nel caso particolare in cui il nodo precedente e quello selezionato coincidano non viene effettuata alcuna operazione.
 */
extern "C" gboolean button_press_handler(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{
    assert(grafo_corrente != NULL);
    
    if(event->type == GDK_BUTTON_PRESS)
    {
        if(event->button == 1)
        {
            grafo_corrente->lmb_premuto(event->x, event->y);
            aggiorna_grafo();
        } else if(event->button == 3)
        {
            Nodo* selezionato = (Nodo *) grafo_corrente->find_by_xy(event->x, event->y);
            if(selezionato == NULL)
                grafo_corrente->aggiungi_nodo(event->x, event->y);
            else
                grafo_corrente->aggiungi_arco(selezionato);
            aggiorna_grafo();
        }
        
    }   
    
    return FALSE;
}

/** Comunica agli oggetti del ::Grafo che il pulsante del mouse è stato rilasciato.
 */ 
extern "C" gboolean button_release_handler(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{
    assert(grafo_corrente != NULL);
    grafo_corrente->lmb_rilasciato();
    
    return FALSE;
}

/** Funzione principale del programma.
 * Viene caricata l'interfaccia grafica dal file graphpath.glade.
 *
 * Vengono connessi tutti i segnali:
 *
 * In particolare i seguenti eventi si occupano di gestire l'interazione con l'interfaccia grafica.
 *  - ::button_press_handler
 *  - ::button_release_handler
 *  - ::motion_notify_handler
 *  - ::draw_handler
 *
 */

int main (int argc, char *argv[])
{
    grafo_corrente = new Grafo();
    grafo_corrente->inizializza();

    gtk_init(&argc, &argv);

    /* crea un nuovo oggetto GtkBuilder */
    builder = gtk_builder_new();

	/* carichiamo l'interfaccia */
    gtk_builder_add_from_file(builder,"graphpaths.glade", NULL);

    /*
     * connessione di tutti i segnali,
     */
    gtk_builder_connect_signals(builder, NULL);

    append_log("Benvenuto in GraphPath\n");
    append_log("Puoi iniziare a creare un grafo cliccando col tasto destro all'interno dell'area di disegno.\n");

    /* partenza del ciclo di gestione degli eventi */
    gtk_main();
   
    return 0;
}
